In [1]:
f%%html
<script src="https://rawgithub.com/mrdoob/three.js/master/build/three.js"></script>



In [2]:
%%javascript
require(["notebook/js/widget"], function() {

    var GeometryModel = Backbone.AssociatedModel.extend({});
    IPython.widget_manager.register_widget_model('GeometryModel', GeometryModel);

    var SphereGeometryModel = Backbone.AssociatedModel.extend({});
    IPython.widget_manager.register_widget_model('SphereGeometryModel', SphereGeometryModel);

    var MaterialModel = Backbone.AssociatedModel.extend({});
    IPython.widget_manager.register_widget_model('MaterialModel', MaterialModel);

    var MeshModel = Backbone.AssociatedModel.extend({
          relations: [
      {type: Backbone.One,
        key: 'geometry',
        relatedModel:SphereGeometryModel},
      {type: Backbone.One,
        key: 'material',
        relatedModel:MaterialModel},
      ],
    });
    IPython.widget_manager.register_widget_model('MeshModel', MeshModel);
    var CameraModel = Backbone.AssociatedModel.extend({});
    IPython.widget_manager.register_widget_model('CameraModel', CameraModel);   

    var SceneModel = Backbone.AssociatedModel.extend({
      relations: [
      {type: Backbone.Many,
        key: 'meshes',
        relatedModel:MeshModel},
      ],
    });
    IPython.widget_manager.register_widget_model('SceneModel', SceneModel);

    var RendererModel = IPython.WidgetModel.extend({
      relations: [
      {type: Backbone.One,
        key: 'scene',
        relatedModel:SceneModel},
      {type: Backbone.One,
        key: 'camera',
        relatedModel:CameraModel},
      ],
    });
    IPython.widget_manager.register_widget_model('RendererModel', RendererModel);        
})


Goals

Traitlet objects in python, and some way to specify the scene graph relationship. Possibly the scene graph is represented separately from the collection of objects, and possibly the objects form a nested scenegraph

Communication with the front end over a single comm link---so the renderer is the only "widget". Changes to each of the objects is communicated through it.

Events on the javascript side

Possible ways to do it

  • have a flat set of objects and a separate scenegraph showing the relationships between the objects.

    • easy to sync changes (each object has an id; changes contain the id number)
    • fits with backbone better, sort of. We just need a heterogeneous collection of models, plus a scenegraph model.
  • nested set of objects

    • more natural to construct
    • harder to specify events---you have to traverse the hierarchy first

In [2]:


In [2]:


In [3]:
# Import the base Widget class and the traitlets Unicode class.
from IPython.html.widgets.widget import Widget, NonDOMWidget
from IPython.utils.traitlets import Unicode, Int, Instance, Enum, List, Float

class Geometry(NonDOMWidget):
    target_name = Unicode('GeometryModel')
    default_view_name = Unicode('GeometryView')

class SphereGeometry(Geometry):
    target_name = Unicode('SphereGeometryModel')
    default_view_name = Unicode('SphereGeometryView')
    _keys = ['radius']
    radius = Int(100)

class Material(NonDOMWidget):
    target_name = Unicode('MaterialModel')
    default_view_name = Unicode('MaterialView')
    _keys = ['color']
    color = Int(0x00cc00)

class Mesh(NonDOMWidget):
    target_name = Unicode('MeshModel')
    default_view_name = Unicode('MeshView')
    _keys = ['geometry', 'material']
    geometry = Instance(Geometry)
    material = Instance(Material)

class Camera(NonDOMWidget):
    target_name = Unicode('CameraModel')
    default_view_name = Unicode('CameraView')
    _keys = ['fov', 'ratio']
    fov = Int(70)
    ratio = Float(600.0/400.0)
    
class Scene(NonDOMWidget):
    target_name = Unicode('SceneModel')
    default_view_name = Unicode('SceneView') 
    _keys = ['meshes']
    meshes = List(Instance(Mesh))

class Renderer(Widget):
    target_name = Unicode('RendererModel')
    default_view_name = Unicode('RendererView')
    _keys = ['width', 'height', 'renderer_type', 'scene', 'camera']
    width = Int(600)
    height = Int(400)
    renderer_type = Enum(['webgl', 'canvas', 'auto'], 'auto')
    scene = Instance(Scene)
    camera = Instance(Camera)


---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-3-d48097e787ba> in <module>()
      1 # Import the base Widget class and the traitlets Unicode class.
----> 2 from IPython.html.widgets.widget import Widget, NonDOMWidget
      3 from IPython.utils.traitlets import Unicode, Int, Instance, Enum, List, Float
      4 
      5 class Geometry(NonDOMWidget):

ImportError: cannot import name NonDOMWidget

In [4]:
%%javascript
require(["notebook/js/widget"], function() {
    var RendererView = IPython.WidgetView.extend({
        render : function(){
            var width = this.model.get('width');
            var height = this.model.get('height');
            this.renderer = new THREE.WebGLRenderer();
            this.renderer.setSize( width, height);
            this.$el.empty().append( this.renderer.domElement );
            this.camera = new CameraView({model: this.model.get('camera')});
            this.camera.render();
            this.scene = new SceneView({model: this.model.get('scene')});
            this.scene.render();
            console.log('renderer', this.model, this.scene.obj);
        },
        update : function(){
            console.log('update');
            // TODO: tie into a requestAnimationFrame update (just trigger a frame if one is not already lined up)
            this.renderer.render(this.scene.obj, this.camera.obj);
            return IPython.WidgetView.prototype.update.call(this);
            // perhaps we should just listen to the nested-change event
            // and perform the appropriate modificatino ourselves?
            // this assumes that (1) we've stored the three.js hierarchy exactly as the models are set up
            // and (2) all changes to the three.js objects are exactly the same (e.g., setting attributes)
            // regardless, we should probably listen to the nested-change event to trigger a redraw
        },        
    });

    IPython.widget_manager.register_widget_view('RendererView', RendererView);
    
    var NestedView = function(options) {
        this.cid = _.uniqueId('view');
        options || (options = {});
        _.extend(this, _.pick(options, ['model', 'id']));
        this.initialize.apply(this, arguments);
        this.model.on('change', this.update, this);
        this.visible = true;
    };
    _.extend(NestedView.prototype, Backbone.Events, {
        initialize: function(){},
        render: function() {
          return this;
        },
        remove: function() {
          this.stopListening();
          return this;
        },
    });
    NestedView.extend = Backbone.View.extend;
    var CameraView = NestedView.extend({
        render: function() {
            var camera = this.obj = new THREE.PerspectiveCamera( this.model.get('fov'), this.model.get('ratio'), 1, 1000 );
            camera.position.set(0,150,400);
            return camera;
        }
    });
    
    var SceneView = NestedView.extend({
        render: function() {
            var scene = this.obj = new THREE.Scene();
            var light = new THREE.PointLight(0xffffff);
            light.position.set(100,250,100);
            scene.add(light);
            var that = this;
            this.meshviews = [];
            this.model.get('meshes').each(function(model) {
                var m = new MeshView({model: model})
                that.meshviews.push(m)
                scene.add(m.render());
            })
            this.model.get('lights').each(function(model) {
                var m = new LightView({model: model})
                that.lightviews.push(m);
                scene.add(m.render());
            })
            return scene;
        }    
    });
// TODO: most change events are very similar---just change the three.js object appropriately.
// maybe 
    
    var GeometryView = NestedView.extend({
        render: function() {
            var geometry = this.obj = new THREE.SphereGeometry(this.model.get('radius'), 32, 16);
            return geometry;
        }
        
        // update will need to generate a new mesh (to change the radius...)
    })
    
    var MaterialView = NestedView.extend({
        render: function() {
            var material = this.obj = new THREE.MeshLambertMaterial({color: this.model.get('color')});
            return material;
        }
    })
    
    var MeshView = NestedView.extend({
        render: function() {
            this.geometryview = new GeometryView({model: this.model.get('geometry')});
            this.materialview = new MaterialView({model: this.model.get('material')});
            var mesh = this.obj = new THREE.Mesh( this.geometryview.render(), this.materialview.render() );
            return mesh
        }    
    });   
});



In [5]:
from IPython.display import display
sphere = Mesh(geometry=SphereGeometry(), material=Material())
scene = Scene(meshes=[sphere])
renderer = Renderer(camera=Camera(), scene = scene)
renderer.get_state()
display(renderer)


---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-5-010c9cb05991> in <module>()
      1 from IPython.display import display
----> 2 sphere = Mesh(geometry=SphereGeometry(), material=Material())
      3 scene = Scene(meshes=[sphere])
      4 renderer = Renderer(camera=Camera(), scene = scene)
      5 renderer.get_state()

NameError: name 'Mesh' is not defined

In [ ]: